Framework / DOM / Documents / Styling and Inheritance
In This Topic
    Styling and Inheritance
    In This Topic
     CSS Overview

    The DOM Styling and Inheritance refers to the build-in ability of certain element properties to have computed values specified trough style sheets or to be inherited from the computed values of their ancestor elements. The DOM Styling and Inheritance is abbreviated as CSS, since it is inspired by the W3C Cascade Style Sheets specification.

    The CSS in general represents a language for building rules that let you specify the computed property values of elements, based on their type, current state, placement in the elements hierarchy, relation with other elements etc. The following image represents the object diagram of the DOM Style Sheets:

    figure 1. Style Sheets Object Diagram

    The style sheets of a document contain rules. Each rule holds two collections - a collection of selectors and a collection of declarations. The declarations of a specific rule applies to a specific element, if at least one of the selectors of that rule matches the element. The declarations collection is generally a map of a DOM property (see Properties) to the value that this property must have.

     My First CSS

    To better understand how CSS works consider the following example:

    My First CSS
    Copy Code
    public class MyElementA : NElement
    {
        public MyElementA() { }
        static MyElementA()
        {
            MyElementASchema = NSchema.Create(typeof(MyElementA), NElement.NElementSchema);
            MyElementASchema.MakeContainer();
            BChild = MyElementASchema.AddChild("B", typeof(MyElementB));
            A1Property = MyElementASchema.AddSlot("A1", NDomType.Int32, 0);
            A1Property.SetStyleable(true);
        }
        public int A1
        {
            get { return (int)GetValue(A1Property); }
            set { SetValue(A1Property, value); }
        }
        public MyElementB B
        {
            get { return (MyElementB)GetChild(BChild); }
            set { SetChild(BChild, value); }
        }
        public static readonly NProperty A1Property;
        public static readonly NChild BChild;
        public static readonly NSchema MyElementASchema;
    }
    public class MyElementB : NElement
    {
        public MyElementB() { }
        static MyElementB()
        {
            MyElementBSchema = NSchema.Create(typeof(MyElementB), NElement.NElementSchema);
            A1Property = MyElementBSchema.AddSlot("A1", NDomType.Int32, 0);
            A1Property.SetStyleable(true);
            A1Property.SetInherited(true);
        }
        public int A1
        {
            get { return (int)GetValue(A1Property); }
            set { SetValue(A1Property, value); }
        }
        public static readonly NProperty A1Property;
        public static readonly NSchema MyElementBSchema;
    }
    ...
    public static void TestCSS1()
    {
        // create a document the content of which is element A,
        // which on its turns contains a child element B.
        NGenericDocument<MyElementA> myDoc = new NGenericDocument<MyElementA>();
        MyElementA elementA = new MyElementA();
        myDoc.Content = elementA;
        MyElementB elementB = new MyElementB();
        elementA.B = elementB;
        // create a CSS that applies a value of 10 to elements of type A
        // and a value of 20 of elements of type B
        NStyleSheet sheet = new NStyleSheet();
        myDoc.StyleSheets.Add(sheet);
        {
            NRule rule1 = new NRule();
            sheet.Add(rule1);
            NSelector selector1 = new NSelector();
            rule1.Selectors.Add(selector1);
            selector1.Conditions.Add(new NTypeCondition(MyElementA.MyElementASchema));
            rule1.Declarations.Add(= new NValueDeclaration<int>("A1", 10));
        }
        {
            NRule rule2 = new NRule();
            sheet.Add(rule2);
            NSelector selector2 = new NSelector();
            rule2.Selectors.Add(selector2);
            selector2.Conditions.Add(new NTypeCondition(MyElementB.MyElementBSchema));
            rule2.Declarations.Add(new NValueDeclaration<int>("A1", 20));
        }
        // evaluate the document and test the results
        myDoc.Evaluate();
        Console.WriteLine("ElementA property A1 value is: " + elementA.A1);
        Console.WriteLine("ElementB property A1 value is: " + elementB.A1);
        // Output is:
        // ElementA property A1 value is: 10
        // ElementB property A1 value is: 20
    }
    ...
    public static void TestCSS2()
    {
        // create a document the content of which is element A,
        // which on its turns contains a child element B.
        MyDocument myDoc = new MyDocument();
        MyElementA elementA = new MyElementA();
        myDoc.Content = elementA;
        MyElementB elementB = new MyElementB();
        elementA.B = elementB;
        // create a CSS that applies a value of 10 to elements of type A
        // and see that the value of element B is inherited
        NStyleSheet sheet = new NStyleSheet();
        myDoc.StyleSheets.Add(sheet);
        {
            NRule rule1 = new NRule();
            sheet.Add(rule1);
            NSelector selector1 = new NSelector();
            rule1.Selectors.Add(selector1);
            selector1.Conditions.Add(new NTypeCondition(MyElementA.MyElementASchema));
            rule1.Declarations.Add(new NValueDeclaration<int>("A1", 10));
        }
        // evaluate the document and test the results
        myDoc.Evaluate();
        Console.WriteLine("ElementA property A1 value is: " + elementA.A1);
        Console.WriteLine("ElementB property A1 value is: " + elementB.A1);
        // Output is:
        // ElementA property A1 value is: 10
        // ElementB property A1 value is: 10
    }
    

    MyElementA is a container node that can contains a single child, which needs to be an instance of the MyElementB class. Both MyElementA and MyElementB have a single property with name A1 of type Int32. The A1 property of MyElementA is marked as stylable. The A1 property of MyElementB is marked as stylable and inherited.

    The TestCSS1 method first creates a simple document that contains an element of type MyElementA, which on its turn contains an element of type MyElementB. Then it creates a single style sheet that has two rules:

    • The first rule matches elements of type MyElementA and applies a value of 10 to stylable properties with name A1 and type Int32.
    • The second rule matches elements of type MyElementB and applies a value of 20 to stylable properties with name A1 and type Int32.

    After the document evaluation, the computed value of the elementA.A1 property is 10, while the computed value of the elementB.A1 property is 20. Both values have been specified trough styling.

    The TestCSS2 method is identical to the TestCSS1 method except that it does not create a rule that applies a value to the A1 property of elements of type MyElementB. After the document evaluation the computed value of the elementA.A1 property is 10, while the computed value of the elementB.A1 property is also 10. The elementA.A1 property has been specified trough styling, while the elementB.A1 property value was inherited.

     Selectors 

    The power of CSS is in the ability to construct selectors that perform complex element pattern matching. Selectors achieve this by coupling conditions and combinators. The following image illustrates the object diagram of a selector:

    figure 2. Selectors Object Diagram

    The condition collection of a selector holds the selector key conditions. In order for a selector to match a specific element, that element must match all key conditions.

    The combinator collection is optionally populated with combinators. Each combinator way also have conditions that are added in its condition collection. Provided that a specific element matches the selector key conditions, the selector tries to satisfy the combinators next. Each combinator in the collection of combinators may switch the current element for matching, in a combinator specific manner (e.g. switch the target for matching to the parent element of the original target element, to an ancestor element etc.). If the current target element matches the current combinator, the selector tries to satisfy the next combinator. 

    The selector is said to match a specific element, if the element matches all key conditions and all selector combinators.

    There is a certain specificity ranking associated with the CSS conditions. The conditions specificity rankings are used to calculate the selector specificity. The selector specificity is one of the factors that plays an important role in the weight of a CSS declaration (described later in this topic). The selector specificity can be calculated by first getting all conditions that reside in the selector subtree (key conditions and conditions that belong to combinators) and then evaluating the following formula:

    (A * 100) + (B * 10) + (C * 1)

    where:

    A = count of conditions with High Specificity Rank
    B = count of conditions with Normal Specificity Rank
    C = count of conditions with Low Specificity Rank

    All types of conditions derive from the base NCondition class. The following table summarizes the conditions currently implemented by the DOM:

    Condition Description W3C Equivalent Default Specificity Rank

    NTypeCondition

    A condition, which matches, if the element is an instance of a certain schema (type), or a derived one. Equivalent to Type condition, with the difference that W3C CSS does not have polymorphism in general. Low

    NFirstChildCondition

    A condition, which matches, if the element is the first child of its parent. Equivalent to the ":first-child" pseudo-class condition. Normal

    NLastChildCondition

    A condition, which matches, if the element is the last child of its parent. Equivalent to the ":last-child" pseudo-class condition. Normal

    NNamedChildCondition

    A condition, which matches, if the element is a named child of its parent and is exposed by a child slot with a certain name. None Normal

    NValueEqualsCondition<T>

    A condition, which matches, if the element value for the property with the specified name and generic type is equal to the specified value. Similar to the CSS [att=val] condition, with the difference that it also matches the element, if it does not have a local value, but the default property value is equal to the value. Normal

    NContainsValueCondition<T>

    A condition, which matches, if the element contains a local value for a property with the specified name. Equivalent to the [att] condition. Normal

    NUserClassCondition

    A condition, which matches, if the element UserClass property contains the specified UserClass. Equivalent to the CSS Class condition. Normal

    NUserIdCondition

    A condition, which matches, if the element UserId property is equal to the specified UserId. Equivalent to the CSS Id condition. High

    Common for all conditions is that they can be marked as inverted, which is achieved by setting their Inverted property to true. Inverted conditions have the meaning of logical not, an are equivalent to the not statement in WC3. In NOV CSS you can change the specificity rank of each condition by setting its SpecificityRank property. NOV CSS also supports the None specificity rank, that allows you to exclude certain conditions from the selector specificity calculation.

    All types of combinators derive from the base NCombinator class. The following table summarizes the combinators currently implemented by the DOM:

    Combinator Description W3C Equivalent

    NChildCombinator

    A combinator, which matches the element in the context of its parent
    (e.g. match the element if it is a child of a specific parent).
    Equivalent to CSS '>' Child Combinator.

    NDescendantCombinator

    A combinator, which matches the element in the context of its ancestors
    (e.g. match the element if it is a descendant of a specific ancestor).
    Equivalent to CSS ' ' Descendant Combinator.

    NNextAdjacentSiblingCombinator

    A combinator, which matches the element in the context of its adjacent previous sibling.
    (e.g. match the element if it is a next sibling of a specific previous sibling).
    Equivalent to CSS '+' Adjacent Sibling Combinator.

    NNextGeneralSiblingCombinator

    A combinator, which matches the element in the context of its previous siblings.
    (e.g. match the element if it is a next sibling of a specific previous sibling).
    Equivalent to CSS '~' General Sibling Combinator.

    NPrevAdjacentSiblingCombinator

    A combinator, which matches the element in the context of its adjacent next siblings.
    (e.g. match the element if it is a previous sibling of a specific next sibling).
    None.

    NPrevGeneralSiblingCombinator

    A combinator, which matches the element in the context of its next siblings.
    (e.g. match the element if it is a previous sibling of a specific next sibling).
    None.

    In practice you will build selectors by using an instance of the NSelectorBuilder class, that can be obtained by the GetSelectorBuilder() method of each NRule, as shown in the following example:

    Using the NSelectorBuilder
    Copy Code
    // the following selector builder code...
    NSelectorBuilder sb = rule.GetSelectorBuilder();
    sb.Start();
    sb.Type(typeof(MyElementB));
    sb.ChildOf();
    sb.Type(typeof(MyElementB));
    sb.End();
    // ...is equivalent to
    NSelector selector = new NSelector();
    rule.Selectors.Add(selector);
    selector.Conditions.Add(new NTypeCondition(typeof(MyElementB)));
    NChildCombinator childCombinator = new NChildCombinator();
    selector.Combinators.Add(childCombinator);
    childCombinator.Conditions.Add(new NTypeCondition(typeof(MyElementA)));
    
    An alternative way to create entire style sheets are themes. See Themes for more info.
     Declarations

    All types of declarations derive from the base NDeclaration class. The following table summarizes the declarations currently implemented by the DOM:

    Declaration Description

    NValueDeclaration<T>

    Represent a DOM property - value pair, where the value is of the generic type.

    NInheritDeclaration

    Represent a declaration, which forces the DOM property value to be inherited. This is equivalent to the W3C inherit value.
    Common for all types of declarations is that they can be declared as important by setting their Important property to true. Important declarations are with altered weight calculation as explained below.
     Medias

    There is a certain media type associated with each document. Media types are represented by singleton instances of the NMedia object. Currently the DOM supports the following medias:

    • NMedia.Screen - the media type associated with desktop computer screens.
    • NMedia.Print - the media type associated with printing devices.

    The current media associated with a specific document can be obtained from the NDocument-GetEffectiveMedia() method. If the document does not have an explicit local media specified, through its Media property, the method returns the media of the owner document, unless the InheritMedia property is set to false. The default media is the null (universal media).

    Style sheets and rules can be authored to target specific medias. The specification whether a style sheet or rule applies to a specific media is performed by a NMediaSelector instance that can be assigned to the style sheet or rule via its MediaSelector property. The following code example associates a media selector to a style sheet and instructs CSS to consider the style sheet rules, only if the document effective media is NMedia.Print

    Style Sheets Media Selectors
    Copy Code
    NStyleSheet sheet = new NStyleSheet();
    sheet.MediaSelector = new NMediaSelector(ENMediaType.Print);
    
    The style sheet media selector takes precedence over the rules media selectors. This means that if the style sheet media selector does not match the document effective media, all rules in this style sheet will be ignored from the cascade, regardless of whether their media selector matches the media or not.
     The CSS Property Cascade

    In order to understand how CSS determines the final computed value for an element property, you should be familiar with the algorithm that CSS uses to find a computed value proposal that is specified by styling. This algorithm is known as The CSS Property Cascade or simply the cascade.

    The purpose of the cascade is to determine a proposed value for a specific element:property and to associate a certain weight to that value. Following is an overview of the algorithm that CSS uses to find that value, if the element:property is marked as stylable:

    1. Find all declarations that apply to the element property in question.
      In the Properties topic we have explained that each property declared for a node, has an associated DOM property. This association allows for the CSS to actually work with DOM properties, rather than concrete element properties. Notice that declarations are actually specified as a DOM property:value pairs.

      The cascade includes the declarations of the local style sheets (i.e. the style sheets that belong to the element owner document). If the document InheritStyleSheets property is true, the cascade will also include the declarations of the owner document cascade. These declaration will be considered from an inherited origin. The origin of a declaration is considered in step 5.
    2. Remove all declarations that do not match the effective document media.
      The Medias section of this document we have described that you can author style sheets and rules that only target specific medias. This step of the cascade honors the style sheets and rules media selectors. 
    3. Remove all declarations which belong to rules, whose selectors do not match the element.
      In order for a declaration to be included in the cascade it must belong to such a rule, at least one of the selectors of which matches the element.
    4. Associate each declaration with the highest matching selector specificity.
      There may be cases when multiple selectors that belong to a rule simultaneously match an element. In such cases the declaration is associated with the specificity of the matching selector, which has the greatest specificity. The selector specificity was discussed in the Selectors section and is taken into account in step 6.
    5. Sort declarations according to origin and importance (normal or important).
      As mentioned, declarations that originate from style sheets of the document owner document, are considered to be of inherited origin. Since the document owner document can too inherit style sheets from its owner document, there is also a certain inheritance level associated with declarations of inherited origin.

      Say for example that we have document C, which is embedded in document B, which on its turn is embedded in document A. The document C cascade after this sorting will produce the following declaration groups (in descending order).

      document A, important declarations inherited at level 2
      document B, important declarations inherited at level 1
      document C, important declarations from local style sheets
      document C, normal declarations from local style sheets
      document B, normal declarations inherited at level 1
      document A, normal declarations inherited at level 2

      It now becomes clear that important declarations are always winning over normal declarations. The order of inherited important declarations is also reversed, meaning that inherited important declarations will beat normal important declarations.

      For the element:property in question only the group of declarations that belongs to the top most origin:importance group will be considered in the final step.
    6. Sort the declarations in each group by specificity and declaration order.
      There may be multiple declarations from the same origin:importance group, so the final step of the cascade first sorts the declarations in each group by specificity. If there are several declaration with the same specificity, these declarations are ordered by the declaration order in reverse order (i.e. the last declared value wins). The top most declaration after this final sorting wins.

    The cascade tries to find a proposed value specified by styling and associates a certain weight to it. Three more factors remain to be considered for the final element:property computed value to be determined. They are:

    1. The element:property explicit local value.
      If the element:property has an explicit local value (such a value that is explicitly set by calling the SetValue method - see Properties), CSS compares the weight of the local value with the weight of the value proposed by the cascade. CSS considers explicit local values to be members of the normal declarations from local style sheets origin group, and assigns a specificity of 100 to it (as if the declaration originates from a High specificity selector). Furthermore CSS considers explicit local values to have the greatest declaration order (so that explicit local values beat even cascade proposed values of High specificity selectors, due to declaration order).
      If the explicit local value weight beats the cascade proposed value weight, the final computed value is the local value.
       
    2. The element:property inherited value.
      If the element:property is marked as inherited and there is no explicit local value and CSS did not provide a cascade proposed value or the cascade proposed value forces inheritance, then CSS tries to obtain the value by inheritance. This is achieved by looking for a computed value inside the ancestors of the element.
    3. The element:property default value.
      If the element has no explicit local value and CSS failed to provide a cascade proposed value and failed to inherit a computed value, the default element:property value becomes the computed value.

    From the above explanations we can conclude that CSS in general is a sophisticated database for property values sorted by a specific weight. The following image illustrates the final CSS value weight scale:

    The NOV CSS is compliant with the W3C recommendations. NOV CSS features Nevron proprietary optimizations and successfully rivals with the CSS engines of all modern browsers.
    See Also